package sim;
import java.util.TreeMap;

/**
 * Class containing the map between opcode/funct fields and instruction classes/types
 * @author Piotr
 *
 */
public class InstructionDecoder {
	private TreeMap<InstructionType, InstructionClass> iClassMap;
	private TreeMap<String, InstructionType> iTypeMap;
	
	public InstructionDecoder() {
		iClassMap = new TreeMap<InstructionType, InstructionClass>();
		iTypeMap = new TreeMap<String, InstructionType>();
		
		// Set all supported opcodes
		iTypeMap.put("0000", InstructionType.ADD);
		iTypeMap.put("0001", InstructionType.SUB);
		iTypeMap.put("0010", InstructionType.AND);
		iTypeMap.put("0011", InstructionType.OR);
		iTypeMap.put("0100", InstructionType.NOR);
		iTypeMap.put("0101", InstructionType.MUL);
		iTypeMap.put("0110", InstructionType.SLL);
		iTypeMap.put("0111", InstructionType.SRL);
		iTypeMap.put("1000", InstructionType.SRA);
		iTypeMap.put("1001", InstructionType.LI);
		iTypeMap.put("1010", InstructionType.ADDI);
		iTypeMap.put("1011", InstructionType.LOAD);
		iTypeMap.put("1100", InstructionType.STORE);
		iTypeMap.put("1101", InstructionType.BEZ);
		iTypeMap.put("1110", InstructionType.BLZ);
		iTypeMap.put("1111", InstructionType.JALR);
		
		// Set all instruction classes
		iClassMap.put(InstructionType.ADD, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.SUB, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.AND, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.OR, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.NOR, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.MUL, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.NOP, InstructionClass.RTYPE);
		iClassMap.put(InstructionType.SLL, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.SRA, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.SRL, InstructionClass.SHIFT);
		iClassMap.put(InstructionType.LI, InstructionClass.IMM);
		iClassMap.put(InstructionType.ADDI, InstructionClass.IMM);
		iClassMap.put(InstructionType.LOAD, InstructionClass.MEM);
		iClassMap.put(InstructionType.STORE, InstructionClass.MEM);
		iClassMap.put(InstructionType.BEZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.BLZ, InstructionClass.BRANCH);
		iClassMap.put(InstructionType.JALR, InstructionClass.BRANCH);
	}
	
	/**
	 * Checks if the supplied opcode corresponds to a valid instruction.
	 * @param opcode
	 * @return true for valid inputs
	 */
	public boolean isValid(String opcode) {
		if (iTypeMap.containsKey(opcode)) {
			return true;
		} else
			return false;
	}
	
	/**
	 * Returns the InstructionType corresponding to the opcode.
	 * @param opcode
	 * @return InstructionType
	 */
	public InstructionType decodeType(String opcode) {
			return iTypeMap.get(opcode);
	}
	
	/**
	 * Returns the InstructionClass for the given InstructionType.
	 * @param type
	 * @return corresponding InstructionClass
	 */
	public InstructionClass decodeClass(InstructionType type) {
		return iClassMap.get(type);
	}
	
	/**
	 * Returns the InstructionClass for the given opcode.
	 * @param opcode
	 * @return corresponding InstructionClass
	 */
	public InstructionClass decodeClass(String opcode) {
		InstructionType type = iTypeMap.get(opcode);
		return iClassMap.get(type);
	}
	
	/**
	 * Decodes bitString value to fill in necessary fields.
	 * @return 0 if successful, 1 if invalid instruction
	 */
	public int decodeInstruction(Instruction instr) {
		String opcode = new String(instr.bitString.substring(0, 4));
		//System.out.println(opcode);
		if (isValid(opcode)) {
			//System.out.println("Opcode valid");
			instr.iType = decodeType(opcode);
			instr.iClass = decodeClass(instr.iType);
			decodeRegNumbers(instr);
			return 0;
		} else
			//System.out.println("Opcode invalid");
			return 1;
	}
	
	/**
	 * Fills in register args/values using rs, rt, rd, shift, imm fields in instruction
	 * Fills in:
	 * @param instr
	 */
	private void decodeRegNumbers(Instruction instr) {
		int temp;
	
		instr.dest1Reg = -1;
		instr.dest2Reg = -1;
		instr.arg1Reg = -1;
		instr.arg2Reg = -1;
	
		switch (instr.iClass) {
			case RTYPE: 			//r-type instructions: add, sub, and, or, nor, mul
				temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
				temp = toUnsigned(temp, 4);
				instr.dest1Reg = temp;
				if (instr.iType == InstructionType.MUL) {
					instr.dest2Reg = (temp < 15 ? temp + 1 : -1);  //Most significant half
				}
				temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
				temp = toUnsigned(temp, 4);
				instr.arg1Reg = temp;	
				temp = Integer.parseInt(instr.bitString.substring(12,16), 2);
				temp = toUnsigned(temp, 4);
				instr.arg2Reg = temp;
				break;
				
			case IMM: 		//Instructions with immediate
				temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
				temp = toUnsigned(temp, 4);
				instr.dest1Reg = temp;
				if (instr.iType == InstructionType.ADDI) {
					temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
					temp = toUnsigned(temp, 4);
					instr.arg1Reg = temp;	//Operand1
					temp = Integer.parseInt(instr.bitString.substring(12,16), 2);
					instr.arg2Val = toSigned(temp, 4);
					instr.arg2Ready = true;
				} else {	// LI
					instr.arg1Reg = instr.dest1Reg;
					temp = Integer.parseInt(instr.bitString.substring(8,16), 2);
					temp = toUnsigned(temp, 8);
					instr.arg2Val = temp;	//Immediate
					instr.arg2Ready = true;
				}
				break;

			case SHIFT: 			//Shift instructions
				temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
				temp = toUnsigned(temp, 4);
				instr.dest1Reg = temp;
				temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
				temp = toUnsigned(temp, 4);
				instr.arg1Reg = temp;		//Value to be shifted
				temp = Integer.parseInt(instr.bitString.substring(12,16), 2);
				temp = toUnsigned(temp, 4);
				instr.arg2Val = temp + 1;	//Shift amount
				instr.arg2Ready = true;
				break;
			case MEM:				//Load/Store instructions
				if (instr.iType == InstructionType.LOAD) {
					temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
					temp = toUnsigned(temp, 4);
					instr.dest1Reg = temp;	//Destination
					temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
					temp = toUnsigned(temp, 4);
					instr.arg1Reg = temp;		//Base address
					temp = Integer.parseInt(instr.bitString.substring(12,16), 2);
					instr.arg2Val = toSigned(temp, 4);	//Immediate offset
					instr.arg2Ready = true;
				} else {	//Store
					temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
					temp = toUnsigned(temp, 4);
					instr.arg1Reg = temp;	//Register to be stored
					temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
					temp = toUnsigned(temp, 4);
					instr.arg2Reg = temp;	//Base address
					temp = Integer.parseInt(instr.bitString.substring(12,16), 2);
					instr.arg3Val = toSigned(temp, 4);	//Immediate offset
				}
				break;
			case BRANCH: 			//Branch/jump instructions
				if (instr.iType == InstructionType.JALR) {
					temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
					temp = toUnsigned(temp, 4);
					instr.dest1Reg = temp;	//Destination for PC+1
					temp = Integer.parseInt(instr.bitString.substring(8,12), 2);
					temp = toUnsigned(temp, 4);
					instr.arg1Reg = temp;	//Absolute branch target
					
				} else {	// BEZ or BLZ
					temp = Integer.parseInt(instr.bitString.substring(4,8), 2);
					temp = toUnsigned(temp, 4);
					instr.arg1Reg = temp;	//Value to be compared
					temp = Integer.parseInt(instr.bitString.substring(8,16), 2);
					instr.arg2Val = toSigned(temp, 8);	//Relative branch target
					instr.arg2Ready = true;
				}
				break;
		}
	}
	
	/**
	 * Takes a signed int and removes the sign-extended ones if it was negative given
	 * the length.
	 * @param x
	 * @param length
	 * @return
	 */
	private int toUnsigned(int x, int length) {
		if (x >= 0) {
			return x;
		} else {
			return (int)Math.pow(2, length) + x; 
		}
	}
	
	private int toSigned(int x, int length) {
		int maxPos = (int)Math.pow(2, length-1) - 1;
		if (x > maxPos) {
			return x - (2*(maxPos + 1));
		} else {
			return x;
		}
	}
}
